package com.love.cloud.common.mybatis.interceptor.mybatis;


import com.love.cloud.common.mybatis.annotation.DataAuth;
import com.love.cloud.common.mybatis.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;


/**
 * 数据权限拦截器
 * 根据各个微服务,继承DataAuthService增加不同的where语句
 * @author hongdongqu
 */
@Component
@Intercepts({@Signature(method = "query",type = Executor.class,args ={
         MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
@Slf4j
public class MybatisDataAuthInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation arg0) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];
        // 只对查询sql拦截
        if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            return arg0.proceed();
        }
        // String mSql = sql;
        // 注解逻辑判断 添加注解了才拦截追加
        Class<?> classType = Class.forName(mappedStatement.getId()
                .substring(0,
                        mappedStatement.getId().lastIndexOf(".")));
        String mName = mappedStatement.getId()
                .substring(mappedStatement.getId()
                        .lastIndexOf(".") +
                        1, mappedStatement.getId().length()); //
        for (Method method : classType.getDeclaredMethods()) {
            if (method.isAnnotationPresent(DataAuth.class) &&
                    mName.equals(method.getName())) {
                /**
                 * 查找标识了该注解 的实现 类
                 */
                Map<String, Object> beanMap = SpringUtil.getApplicationContext().getBeansWithAnnotation(DataAuth.class);
                if ((beanMap != null) && (beanMap.entrySet().size() > 0)) {
                    for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
                        DataAuth action = method.getAnnotation(DataAuth.class);
                        if (StringUtils.isEmpty(action.method())) {
                            break;
                        }
                        try {
                            Method md = entry.getValue().getClass()
                                    .getMethod(action.method(),
                                            new Class[]{String.class});
                            /**
                             * 反射获取业务 sql
                             */
                            String whereSql = (String) md.invoke(SpringUtil.getBean(
                                    entry.getValue().getClass()),
                                    new Object[]{action.tableAlias()});
                            if (!StringUtils.isEmpty(whereSql) &&
                                    !"null".equalsIgnoreCase(whereSql)) {
                                Object parameter = null;
                                if (arg0.getArgs().length > 1) {
                                    parameter = arg0.getArgs()[1];
                                }
                                BoundSql boundSql = mappedStatement.getBoundSql(parameter);
                                MappedStatement newStatement = newMappedStatement(mappedStatement,
                                        new BoundSqlSqlSource(boundSql));
                                MetaObject msObject = MetaObject.forObject(newStatement,
                                        new DefaultObjectFactory(),
                                        new DefaultObjectWrapperFactory(),
                                        new DefaultReflectorFactory());
                                /**
                                 * 通过JSqlParser解析 原有sql,追加sql条件
                                 */
                                CCJSqlParserManager parserManager = new CCJSqlParserManager();
                                Select select = (Select) parserManager.parse(new StringReader(
                                        boundSql.getSql()));
                                PlainSelect selectBody = (PlainSelect) select.getSelectBody();
                                Expression whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql);
                                selectBody.setWhere(new AndExpression(
                                        selectBody.getWhere(),
                                        new Parenthesis(whereExpression)));
                                /**
                                 * 修改sql
                                 */
                                msObject.setValue("sqlSource.boundSql.sql",
                                        selectBody.toString());
                                arg0.getArgs()[0] = newStatement;
                                log.info("Interceptor sql:" +
                                        selectBody.toString());
                            }
                        } catch (Exception e) {
                            log.error(null, e);
                        }
                        break;
                    }
                }
            }
        }
        return arg0.proceed();
    }

    private MappedStatement newMappedStatement(MappedStatement ms,
                                               SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(),
                ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if ((ms.getKeyProperties() != null) &&
                (ms.getKeyProperties().length != 0)) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1,
                    keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    /**
     * 当目标类是Executor类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties arg0) {
        // TODO Auto-generated method stub
    }

    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
